package com.ibm.cps.sql.select;

import com.ibm.cps.sql.RegexUtil;
import net.sf.jsqlparser.expression.Alias;
import net.sf.jsqlparser.expression.Expression;
import net.sf.jsqlparser.statement.select.SelectExpressionItem;
import net.sf.jsqlparser.statement.select.SelectItemVisitorAdapter;

import java.util.ArrayList;
import java.util.List;
import java.util.regex.Matcher;

/**
 * Created by telekinesis on 6/17/15.
 */
public class SelectionItemVisitor extends SelectItemVisitorAdapter{
    public static final String FUNCTION_TESTER = "\\w*\\(.*\\)";
    public static final String MULTI_FIELD_TESTER = "\\[.*\\]";

    private boolean isFunctionSelection;
    private String outputStream;
    private List<FieldDescriptor> descriptors = new ArrayList<>();

    public void visit(SelectExpressionItem item) {
        Alias alias = item.getAlias();
        if(alias == null){
            throw new IllegalArgumentException("Output topic required (defined by AS clause)");
        }
        this.outputStream = alias.getName();
        Expression fields = item.getExpression();
        String fieldString = fields.toString();
        if(isSingleFunction(fieldString)){
            parseFunction(fieldString);
        }else{
            parseProjection(fieldString);
        }
    }

    private boolean isSingleFunction(String fieldString) {
        Matcher m = RegexUtil.match(fieldString, FUNCTION_TESTER);
        if(!m.find()){
            return false;
        }
        String functionString = m.group();
        if(fieldString.length() != functionString.length()){
            throw new IllegalArgumentException("Only one output field allowed for function output");
        }
        if(functionString.charAt(0) == '('){
            throw new IllegalArgumentException("Missing function name");
        }
        return true;
    }

    private void parseFunction(String functionExpression){
        int index = functionExpression.indexOf('(');
        String functionName = functionExpression.substring(0, index);
        validateFunctionName(functionName);
        String argument = functionExpression.substring(index + 1, functionExpression.length() - 1);
        FieldDescriptor result = new FieldDescriptor(functionName.toUpperCase(), argument);
        descriptors.add(result);
        this.isFunctionSelection = true;
    }

    public enum SupportedFunctions {
        EVENT, DFT, MIN, MAX, AVERAGE, PRICE_VARIATION, VARIANCE,DTW
    }

    private void validateFunctionName(String functionName){
        functionName = functionName.toUpperCase();
        boolean supported = false;
        for(SupportedFunctions f : SupportedFunctions.values()){
            if(functionName.toUpperCase().equals(f.name())){
                supported = true;
            }
        }
        if(!supported){
            throw new IllegalArgumentException("function " + functionName + " is not supported");
        }
    }

    private void parseProjection(String fieldString){
        this.isFunctionSelection = false;
        if(isMultiFields(fieldString)){
            parseMultiFields(fieldString);
        }else{
            parseSingleField(fieldString);
        }
    }

    private boolean isMultiFields(String fieldString){
        Matcher m = RegexUtil.match(fieldString, MULTI_FIELD_TESTER);
        if(!m.find()){
            return false;
        }
        String matched = m.group();
        if(matched.length() != fieldString.length()){
            throw new IllegalArgumentException("Multi field selection should be surrounded by []");
        }
        if(matched.length() == 0){
            throw new IllegalArgumentException("At least one output field should be defined");
        }
        return true;
    }

    private void parseMultiFields(String fieldString){
        fieldString = fieldString.substring(1, fieldString.length() - 1);
        String[] fields = fieldString.split(",");
        for(String f : fields){
            FieldDescriptor descriptor = new FieldDescriptor(f.trim());
            descriptors.add(descriptor);
        }
    }

    private void parseSingleField(String singleField){
        if(singleField.contains(",")){
            throw new IllegalArgumentException("Multi field selection should be surrounded by []");
        }
        FieldDescriptor descriptor = new FieldDescriptor(singleField.trim());
        descriptors.add(descriptor);
    }

    public List<FieldDescriptor> getDescriptors() {
        return descriptors;
    }

    public String getOutputStream() {
        return outputStream;
    }

    public boolean isFunctionSelection() {
        return isFunctionSelection;
    }
}
